﻿#define TRACE

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using mshtml;
using SHDocVw;
using SpicIE.Common;
using SpicIE.Controls;
using SpicIE.Parameter;

namespace SpicIE
{
    /// <summary>
    /// This is the base class from which every new plugin has to be derived of. 
    /// The attributes below are needed for registering this class as a COM object.
    /// The parameter for the attributes are static values defined below and should not be changed.
    /// The class itself must not be changed in any case.
    /// </summary>
    [ComVisible(true),
    ClassInterface(ClassInterfaceType.AutoDual),
    Guid(Constants.HOST_GUID),
    ProgId(Constants.HOST_PROGID)]
    public class Host : Component, IObjectWithSite
    {
        /// <summary>
        /// Enables the tracing of the events
        /// </summary>
        [ComVisible(false)]
        public static TraceSource TraceSink = new TraceSource(SpicIE.Constants.TRACE_SOURCE_NAME, SourceLevels.All);

        public static Configuration ConfigurationInstance = null;

        [CLSCompliant(false)]
        public IWebBrowser2 BrowserRef;

        #region Internal browser pointer
        internal DWebBrowserEvents2_Event BrowserEventsRef;
        internal HTMLDocument BrowserDocument;
        #endregion

        #region Events definition
        
        // Internal Events
        public event WebBrowserEvent_OnPluginLoad OnPluginLoad;
        public event WebBrowserEvent_OnPluginUnLoad OnPluginUnLoad;

        // Browser Events
        public event WebBrowserEvent_NavigateError OnNavigateError;
        public event WebBrowserEvent_NavigateComplete2 OnNavigateComplete;
        public event WebBrowserEvent_OnQuit OnQuit;
        public event WebBrowserEvent_BeforeNavigate2 OnBeforeNavigate;
        public event WebBrowserEvent_DocumentComplete OnDocumentComplete;
        public event WebBrowserEvents_DownloadBegin OnDownloadBegin;
        public event WebBrowserEvents_DownloadComplete OnDownloadComplete;
        public event WebBrowserEvents_FullScreen OnFullScreen;
        public event WebBrowserEvents_TheaterMode OnTheaterMode;
        public event WebBrowserEvents_TitleChange OnTitleChange;
        public event WebBrowserEvents_WindowClosing OnWindowClosing;
        public event WebBrowserEvents_FileDownload OnFileDownload;
        public event WebBrowserEvents_NewWindow2 OnNewWindow;
        public event WebBrowserEvents_Visible OnVisible;
        public event WebBrowserEvents_ProgressChange OnProgressChange;
        public event WebBrowserEvents_PropertyChange OnPropertyChange;
        public event WebBrowserEvents_SetPhishingFilterStatus OnSetPhishingFilterStatus;
        public event WebBrowserEvents_UpdatePageStatus OnUpdatePageStatus;

        [CLSCompliant(false)]
        public event WebBrowserEvents_WindowStateChange OnWindowStateChange;
        [CLSCompliant(false)]
        public event WebBrowserEvents_NewWindow3 OnNewWindowExtended;
        
        #endregion

        #region Properties
        [ComVisible(false)]
        private List<Type> TypesToShow = new List<Type>();

        /// <summary>
        /// Contains all valid properties for the browser object
        /// </summary>
        [ComVisible(false)]
        [CLSCompliant(false)]
        public static Browser BrowserProperties;

        /// <summary>
        /// Contains all valid properties for the html document object
        /// </summary>
        [ComVisible(false)]
        [CLSCompliant(false)]
        public static Document DocumentProperties;
        
        #endregion

        #region Registry Manipulation
        /// <summary>
        /// Write a value for a specific key to the current plugin.dll.config
        /// </summary>
        /// <param name="key">the key name for which the value will be saved</param>
        /// <param name="value">the value for the corrosponding key</param>
        public static void WriteConfigKey(string key, string value)
        {
            try
            {
                if (PluginConfiguration != null)
                {
                    if (PluginConfiguration.AppSettings.Settings[key] != null)
                    {
                        PluginConfiguration.AppSettings.Settings[key].Value = value;
                    }
                    else
                    {
                        PluginConfiguration.AppSettings.Settings.Add(key, value);
                    }
                    
                    PluginConfiguration.Save();
                    ConfigurationManager.RefreshSection("appSettings");
                }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
            }
        }

        /// <summary>
        /// Reads the value for a specified key from the current plugin.dll.config
        /// </summary>
        /// <param name="key">the key whose value shall be retrieved</param>
        /// <returns>the specific value for the given key</returns>
        public static string ReadConfigKey(string key)
        {
            try
            {
                if (PluginConfiguration != null)
                {
                    AppSettingsSection settings = PluginConfiguration.AppSettings;
                    if (settings != null)
                    {
                        KeyValueConfigurationElement kv = settings.Settings[key];
                        if(kv != null)
                            return kv.Value;
                    }
                }
                return string.Empty;
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
                return string.Empty;
            }
        }

        /// <summary>
        /// Deletes a specific key from the current plugin.dll.config
        /// </summary>
        /// <param name="key">the key which shall be deleted</param>
        public static void DeleteConfigKey(string key)
        {
            try
            {
                if (PluginConfiguration != null)
                {
                    AppSettingsSection settings = PluginConfiguration.AppSettings;
                    if (settings != null)
                    {
                        settings.Settings.Remove(key);
                        PluginConfiguration.Save();
                    }
                }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
            }
        }

        /// <summary>
        /// Retrieves the path to the plugin
        /// </summary>
        /// <returns>the full path to the current plugin.dll</returns>
        private static Configuration GetPluginConfiguration()
        {

            try
            {
                var path = GetActivePath();

                if (!String.IsNullOrEmpty(path))
                {
                    string[] files = Directory.GetFiles(path, "*.dll.config", SearchOption.TopDirectoryOnly);
                    foreach (string file in files)
                    {
                        if (!file.Contains("SpicIE.dll.config"))
                        {
                            var fileMap = new ExeConfigurationFileMap();
                            fileMap.ExeConfigFilename = file;
                            return ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
                        }
                    }
                }
                TraceSink.TraceEvent(TraceEventType.Error, 0, "PluginConfiguration could not retrieve any assembly information nor path!");
                return null;
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
                return null;
            }
        }

        /// <summary>
        /// Property for storing the configuration locally
        /// </summary>
        private static Configuration PluginConfiguration
        {
            get
            {
                return (ConfigurationInstance ?? (ConfigurationInstance = GetPluginConfiguration()));
            }
        }

        /// <summary>
        /// Retrieves the current directory where the plugin is executed
        /// </summary>
        /// <returns>the full path to the plugn directory</returns>
        public static string GetActivePath()
        {
            try
            {
                var assemblies = AppDomain.CurrentDomain.GetAssemblies();

                foreach (Assembly assembly in assemblies)
                {
                    bool gac = assembly.GlobalAssemblyCache;

                    if (gac) 
                        continue;

                    var executing = Assembly.GetExecutingAssembly();
                    var referenced = assembly.GetReferencedAssemblies();
                    foreach (var name in referenced)
                    {
                        if (name.FullName != executing.FullName) continue;
                        if (name.FullName.Contains("SpicIE.dll")) continue;
                        var path = assembly.Location;
                        if (!String.IsNullOrEmpty(path))
                        {
                            return Path.GetDirectoryName(path) + Path.DirectorySeparatorChar;
                        }
                    }
                }

                var main = Assembly.GetExecutingAssembly();
                if(main != null)
                {
                    if (!String.IsNullOrEmpty(main.Location))
                    {
                        return Path.GetDirectoryName(main.Location) + Path.DirectorySeparatorChar;
                    }
                }
                TraceSink.TraceEvent(TraceEventType.Error, 0, "GetActivePath could not retrieve any assembly information nor path!");
                return string.Empty;
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
                return string.Empty;
            }
        }

        /// <summary>
        /// Loads the mapped configuration especially for the trace configuration settings
        /// </summary>
        private static void LoadConfiguration()
        {
            Trace.AutoFlush = true;
            try
            {
                if (!String.IsNullOrEmpty(ReadConfigKey("TraceLog")))
                {
                    var log = ReadConfigKey("TraceLog");
                    
                    var twtl = new TextWriterTraceListener(new StreamWriter(File.Open(log, FileMode.Append, FileAccess.Write, FileShare.Write)));
                    twtl.Name = Constants.TRACE_SOURCE_NAME;

                    TraceSink.Listeners.Add(twtl);
                    if (!String.IsNullOrEmpty(ReadConfigKey("TraceLevel")))
                    {
                        TraceSink.Switch.Level = (SourceLevels) Enum.Parse(typeof (SourceLevels), ReadConfigKey("TraceLevel"));
                    }
                    else
                    {
                        TraceSink.Switch.Level = SourceLevels.Error;
                    }
                }
            }
            catch(Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Global constructor for the host object
        /// </summary>
        public Host()
        {
            try
            {
                LoadConfiguration();

                TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: Host instance initiated");
                
                AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
                
                WebBrowserEvents_OnPluginLoad();
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, ex.ToString());
            }            
        }

        /// <summary>
        /// 
        /// </summary>
        ~Host()
        {
            try
            {
                WebBrowserEvents_OnPluginUnLoad();

                //TraceSink.Close();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(ex.ToString(), "SpicIE: Error :"); 
            }
        }
        #endregion
        
        public void ForceShowToolbar(Type type)
        {
            TypesToShow.Add(type);
        }

        /// <summary>
        /// Exception handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            if(e.IsTerminating)
            {
                Release();
            }

            if (e.ExceptionObject != null)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, e.ExceptionObject.ToString());
            }
            else
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: CurrentDomain_UnhandledException Unhandled exception occured ...");
            }
        }

        /// <summary>
        /// Release of the current instance of the browser obejct
        /// </summary>
        private void Release()
        {
            try
            {
                if (BrowserRef != null)
                {
                    Marshal.ReleaseComObject(BrowserRef);
                    Marshal.FinalReleaseComObject(BrowserRef);
                }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - Release browser reference! Exception: " + ex);
            }

            try
            {
                if (BrowserEventsRef != null)
                {
                    Marshal.ReleaseComObject(BrowserEventsRef);
                    Marshal.FinalReleaseComObject(BrowserEventsRef);
                }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - Release browser events reference! Exception: " + ex);
            }
            
        }

        /// <summary>
        /// Navigate to the given URL
        /// </summary>
        /// <param name="url"></param>
        public void Navigate(string url)
        {
            try
            {
                object flags = 0;
                object obj4 = null;
                object obj5 = null;
                object obj6 = null;

                BrowserRef.Navigate(url, ref flags, ref obj4, ref obj5, ref obj6);
            }
            catch(Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "Exception in Host.Navigate :" + ex);
            }
        }

        /// <summary>
        /// Navigate to the given URL in new window
        /// </summary>
        /// <param name="url"></param>
        public void NewWindow(string url)
        {
            try
            {
                object flags = 1;
                object obj1 = null;
                object obj2 = null;
                object obj3 = null;
                object URL = url;

                BrowserRef.Navigate(url, ref flags, ref obj1, ref obj2, ref obj3);
            }
            catch(Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "Exception in Host.NewWindow :" + ex);
            }
        }

        #region Events
        private void WebBrowserEvents_OnPluginLoad()
        {
            try
            {
                if (OnPluginLoad != null)
                    lock (OnPluginLoad) { OnPluginLoad.Invoke(); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnPluginLoad, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnPluginUnLoad()
        {
            try
            {
                if (OnPluginUnLoad != null)
                    lock (OnPluginUnLoad) { OnPluginUnLoad.Invoke(); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnPluginUnLoad, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_NavigateError(object pDisp, ref object URL, ref object frame, ref object statusCode, ref bool cancel)
        {
            try
            {
                if (OnNavigateError != null)
                    lock (OnNavigateError) { OnNavigateError.Invoke(pDisp, ref URL, ref frame, ref statusCode, ref cancel); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_NavigateError, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_NavigateComplete2(object pDisp, ref object URL)
        {
            try
            {
                if (OnNavigateComplete != null)
                    lock (OnNavigateComplete) { OnNavigateComplete.Invoke(pDisp, ref URL); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_NavigateComplete2, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnQuit()
        {
            try
            {
                if (OnQuit != null)
                    lock (OnQuit) { OnQuit.Invoke(); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnQuit, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
        {
            try
            {
                if (OnBeforeNavigate != null)
                    lock (OnBeforeNavigate) { OnBeforeNavigate.Invoke(pDisp, ref URL, ref Flags, ref TargetFrameName, ref PostData, ref Headers, ref Cancel); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_BeforeNavigate, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnDocumentComplete(object pDisp, ref object URL)
        {
            try
            {
                BrowserDocument = BrowserRef.Document as HTMLDocument;

                // set the properties for the html document
                DocumentProperties = new Document(BrowserDocument);

                if (OnDocumentComplete != null)
                    lock (OnDocumentComplete) { OnDocumentComplete.Invoke(pDisp, ref URL); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnDocumentComplete, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnUpdatePageStatus(object pDisp, ref object nPage, ref object fDone)
        {
            try
            {
                if (OnUpdatePageStatus != null)
                    lock (OnUpdatePageStatus) { OnUpdatePageStatus.Invoke(pDisp, ref nPage, ref fDone); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnUpdatePageStatus, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnSetPhishingFilterStatus(int PhishingFilterStatus)
        {
            try
            {
                if (OnSetPhishingFilterStatus != null)
                    lock (OnSetPhishingFilterStatus) { OnSetPhishingFilterStatus.Invoke(PhishingFilterStatus); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnSetPhishingFilterStatus, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnPropertyChange(string szProperty)
        {
            try
            {
                if (OnPropertyChange != null)
                    lock (OnPropertyChange) { OnPropertyChange.Invoke(szProperty); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnPropertyChange, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnProgressChange(int Progress, int ProgressMax)
        {
            try
            {
                if (OnProgressChange != null)
                    lock (OnProgressChange) { OnProgressChange.Invoke(Progress, ProgressMax); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnProgressChange, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnVisible(bool visible)
        {
            try
            {
                if (OnVisible != null)
                    lock (OnVisible) { OnVisible.Invoke(visible); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnVisible, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnNewWindow3(ref object ppDisp, ref bool Cancel, uint dwFlags, string bstrUrlContext, string bstrUrl)
        {
            try
            {
                if (OnNewWindowExtended != null)
                    lock (OnNewWindowExtended) { OnNewWindowExtended.Invoke(ref ppDisp, ref Cancel, dwFlags, bstrUrlContext, bstrUrl); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnNewWindow3, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnNewWindow2(ref object ppDisp, ref bool Cancel)
        {
            try
            {
                if (OnNewWindow != null)
                    lock (OnNewWindow) { OnNewWindow.Invoke(ref ppDisp, ref Cancel); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnNewWindow2, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnFileDownload(bool ActiveDocument, ref bool Cancel)
        {
            try
            {
                if (OnFileDownload != null)
                    lock (OnFileDownload) { OnFileDownload.Invoke(ActiveDocument, ref Cancel); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnFileDownload, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnWindowStateChange(uint dwWindowStateFlags, uint dwValidFlagsMask)
        {
            try
            {
                if (OnWindowStateChange != null)
                    lock (OnWindowStateChange) { OnWindowStateChange.Invoke(dwWindowStateFlags, dwValidFlagsMask); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnWindowStateChange, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnWindowClosing(bool IsChildWindow, ref bool Cancel)
        {
            try
            {
                if (OnWindowClosing != null)
                    lock (OnWindowClosing) { OnWindowClosing.Invoke(IsChildWindow, ref Cancel); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnWindowClosing, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnTitleChange(string text)
        {
            try
            {
                if (OnTitleChange != null)
                    lock (OnTitleChange) { OnTitleChange.Invoke(text); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnTitleChange, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnTheaterMode(bool active)
        {
            try
            {
                if (OnTheaterMode != null)
                    lock (OnTheaterMode) { OnTheaterMode.Invoke(active); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnTheaterMode, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnFullScreen(bool active)
        {
            try
            {
                if (OnFullScreen != null)
                    lock (OnFullScreen) { OnFullScreen.Invoke(active); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnFullScreen, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnDownloadComplete()
        {
            try
            {
                if (OnDownloadComplete != null)
                    lock (OnDownloadComplete) { OnDownloadComplete.Invoke(); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnDownloadComplete, error in invoke: " + ex);
            }
        }

        private void WebBrowserEvents_OnDownloadBegin()
        {
            try
            {
                if (OnDownloadBegin != null)
                    lock (OnDownloadBegin) { OnDownloadBegin.Invoke(); }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "WebBrowserEvents_OnDownloadBegin, error in invoke: " + ex);
            }
        }
        #endregion

        #region IObjectWithSite implementation
        /// <summary>
        /// SetSite implementation to hook up the plugin to the browser
        /// </summary>
        /// <param name="site"></param>
        public void SetSite(object site)
        {
            TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite called");

            if (site is IInputObjectSite)
            {
                TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: Site is IInputObjectSite");
            }
            else if (site is InternetExplorer)
            {
                // this code is executed by the BHO in order to show the toolbar immediatly 
                // after the installation
                TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: Site is InternetExplorer");
            }

            try
            {
                try
                {
                    if (BrowserRef != null)
                    {
                        TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite got reference to browser - releasing");
                        Release();
                    }
                }
                catch(Exception ex)
                {
                    TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - SetSite release of browser reference failed! Exception: " + ex);
                }

                try
                {
                    if (site != null)
                    {
                        TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite getting browser reference and properties");
                        try
                        {
                            // set the reference
                            BrowserRef = site as IWebBrowser2;

                            // set the properties for the browser reference
                            BrowserProperties = new Browser(BrowserRef);
                        }
                        catch (Exception ex)
                        {
                            TraceSink.TraceEvent(TraceEventType.Error, 0,
                                                 "SpicIE: ERROR - SetSite retrieving browser reference failed! Exception: " +
                                                 ex);
                        }

                        // make sure only runs on IE
                        if (!(BrowserRef.FullName.ToUpper().EndsWith("IEXPLORE.EXE")))
                        {
                            Release();
                            return;
                        }
                    }
                    else
                    {
                        TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite getting browser reference and properties CANNOT commit, site equals null!");
                    }
                }
                catch (Exception ex)
                {
                    TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - SetSite handling browser reference failed! Exception: " + ex);
                }
                

                BrowserEventsRef = BrowserRef as DWebBrowserEvents2_Event;
                if (site != null)
                {
                    TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite calling virtual SetSite for controls");
                    // Register the event handler 
                    BrowserEventsRef.BeforeNavigate2 += WebBrowserEvents_BeforeNavigate2;
                    BrowserEventsRef.NavigateComplete2 += WebBrowserEvents_NavigateComplete2;
                    BrowserEventsRef.NavigateError += WebBrowserEvents_NavigateError;
                    BrowserEventsRef.OnQuit += WebBrowserEvents_OnQuit;
                    BrowserEventsRef.DocumentComplete += WebBrowserEvents_OnDocumentComplete;
                    BrowserEventsRef.DownloadBegin += WebBrowserEvents_OnDownloadBegin;
                    BrowserEventsRef.DownloadComplete += WebBrowserEvents_OnDownloadComplete;
                    BrowserEventsRef.OnFullScreen += WebBrowserEvents_OnFullScreen;
                    BrowserEventsRef.OnTheaterMode += WebBrowserEvents_OnTheaterMode;
                    BrowserEventsRef.TitleChange += WebBrowserEvents_OnTitleChange;
                    BrowserEventsRef.WindowClosing += WebBrowserEvents_OnWindowClosing;
                    BrowserEventsRef.WindowStateChanged += WebBrowserEvents_OnWindowStateChange;
                    BrowserEventsRef.FileDownload += WebBrowserEvents_OnFileDownload;
                    BrowserEventsRef.NewWindow2 += WebBrowserEvents_OnNewWindow2;
                    BrowserEventsRef.NewWindow3 += WebBrowserEvents_OnNewWindow3;
                    BrowserEventsRef.OnVisible += WebBrowserEvents_OnVisible;
                    BrowserEventsRef.ProgressChange += WebBrowserEvents_OnProgressChange;
                    BrowserEventsRef.PropertyChange += WebBrowserEvents_OnPropertyChange;
                    BrowserEventsRef.SetPhishingFilterStatus += WebBrowserEvents_OnSetPhishingFilterStatus;
                    BrowserEventsRef.UpdatePageStatus += WebBrowserEvents_OnUpdatePageStatus;

                    try
                    {
                        if (TypesToShow != null)
                        {
                            foreach (var type in TypesToShow)
                            {
                                try
                                {
                                    var control = (Toolbar)Activator.CreateInstance(type, true);

                                    TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite handling " + control.PluginProgID + "(" + control.PluginGuid + ")");
                                    if (control is IObjectWithSite)
                                    {
                                        TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite call in control " + control.PluginProgID + "(" + control.PluginGuid + ") as IObjectWithSite");

                                        ((IObjectWithSite)control).SetSite(site);
                                    }

                                    if ((control is Toolbar) && ((Toolbar)control).Style == Toolbar.ToolbarEnum.ExplorerToolbar)
                                    {
                                        TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: SetSite " + control.PluginProgID + "(" + control.PluginGuid + ") as Toolbar");
                                        //((Toolbar)control).ShowBrowserBar(true);
                                    }
                                }
                                catch (Exception ex)
                                {
                                    TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - TypesToShow: " + ex);
                                }
                                
                            }
                            TypesToShow.Clear();
                            TypesToShow = null;
                        }
                    }
                    catch (Exception ex)
                    {
                        TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - SetSite calling virtual SetSite! Exception: " + ex);
                    }
                    
                }
                else
                {
                    try
                    {
                        TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: Site is null - unregistering");
                        // deregister the event handler
                        BrowserEventsRef.BeforeNavigate2 -= WebBrowserEvents_BeforeNavigate2;
                        BrowserEventsRef.NavigateComplete2 -= WebBrowserEvents_NavigateComplete2;
                        BrowserEventsRef.NavigateError -= WebBrowserEvents_NavigateError;
                        BrowserEventsRef.OnQuit -= WebBrowserEvents_OnQuit;
                        BrowserEventsRef.DocumentComplete -= WebBrowserEvents_OnDocumentComplete;
                        BrowserEventsRef.DownloadBegin -= WebBrowserEvents_OnDownloadBegin;
                        BrowserEventsRef.DownloadComplete -= WebBrowserEvents_OnDownloadComplete;
                        BrowserEventsRef.OnFullScreen -= WebBrowserEvents_OnFullScreen;
                        BrowserEventsRef.OnTheaterMode -= WebBrowserEvents_OnTheaterMode;
                        BrowserEventsRef.TitleChange -= WebBrowserEvents_OnTitleChange;
                        BrowserEventsRef.WindowClosing -= WebBrowserEvents_OnWindowClosing;
                        BrowserEventsRef.WindowStateChanged -= WebBrowserEvents_OnWindowStateChange;
                        BrowserEventsRef.FileDownload -= WebBrowserEvents_OnFileDownload;
                        BrowserEventsRef.NewWindow2 -= WebBrowserEvents_OnNewWindow2;
                        BrowserEventsRef.NewWindow3 -= WebBrowserEvents_OnNewWindow3;
                        BrowserEventsRef.OnVisible -= WebBrowserEvents_OnVisible;
                        BrowserEventsRef.ProgressChange -= WebBrowserEvents_OnProgressChange;
                        BrowserEventsRef.PropertyChange -= WebBrowserEvents_OnPropertyChange;
                        BrowserEventsRef.SetPhishingFilterStatus -= WebBrowserEvents_OnSetPhishingFilterStatus;
                        BrowserEventsRef.UpdatePageStatus -= WebBrowserEvents_OnUpdatePageStatus;

                        Release();
                        
                    }
                    catch (Exception ex)
                    {
                        TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - SetSite unregistering with Site equals null! Exception: " + ex);
                    }
                    return;
                }
            }
            catch( Exception ex )
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - SetSite:" + ex);
            }
        }

        /// <summary>
        /// GetSite implemenation
        /// </summary>
        /// <param name="riid"></param>
        /// <param name="site"></param>
        public void GetSite(ref Guid riid, out object site)
        {
            TraceSink.TraceEvent(TraceEventType.Information, 0, "SpicIE: GetSite called");

            site = null;

            try
            {
                site = null;
                if (BrowserRef != null)
                {
                    var ptrSite = IntPtr.Zero;
                    var ptrUnk = Marshal.GetIUnknownForObject(BrowserRef);
                    Marshal.QueryInterface(ptrUnk, ref riid, out ptrSite);

                    Marshal.Release(ptrUnk);
                    if (!ptrSite.Equals(IntPtr.Zero))
                    {
                        site = ptrSite;
                    }
                    else
                    {
                        Release();
                        Marshal.ThrowExceptionForHR(SpicIE.Constants.E_NOINTERFACE);
                    }
                }
                else
                {
                    Release();
                    Marshal.ThrowExceptionForHR(SpicIE.Constants.E_FAIL);
                }
            }
            catch( Exception ex )
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "SpicIE: ERROR - GetSite (" + ex + ")");
            }
        }
        #endregion

        #region Registration methods
        /// <summary>
        /// This is the function for registering the plugin as COM object. 
        /// This will be achieved by setting certing keys in the registry.
        /// The function must not be changed.
        /// 
        /// In normal cases it would have been sufficient to use the System.Runtime.InteropServices.RegisterTypeForCOMClients
        /// but this would not be enough in this special scenario since there would be some missing keys for implementing a 
        /// functional toolbar.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="clsid"></param>
        /// <param name="progID"></param>
        public static void RegisterCOMObject(Type obj, Guid clsid, string progID)
        {
            try
            {
                var id = clsid.ToString("B");
                var main = Registry.ClassesRoot.CreateSubKey(@"CLSID\" + id);
                var implemented = main.CreateSubKey("Implemented Categories");
                var inproc = main.CreateSubKey("InprocServer32");
                var version = inproc.CreateSubKey("1.0.0.0");
                var progid = main.CreateSubKey("ProgId");

                implemented.CreateSubKey("{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}");

                progid.SetValue("", progID);

                inproc.SetValue("", "mscoree.dll");
                inproc.SetValue("Assembly", obj.Assembly.ToString());
                inproc.SetValue("Class", obj.ToString());
                inproc.SetValue("CodeBase", obj.Assembly.CodeBase);
                inproc.SetValue("RuntimeVersion", obj.Assembly.ImageRuntimeVersion);
                inproc.SetValue("ThreadingModel", "Apartment");

                version.SetValue("Assembly", obj.Assembly.ToString());
                version.SetValue("Class", obj.ToString());
                version.SetValue("CodeBase", obj.Assembly.CodeBase);
                version.SetValue("RuntimeVersion", obj.Assembly.ImageRuntimeVersion);
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "Exception: RegisterCOMObject " + clsid + ": " + ex);
            }
        }

        /// <summary>
        /// This is the function for unregistering the plugin. 
        /// This will be achieved by deleting certein keys in the registry.
        /// The function must not be changed.
        /// 
        /// In normal cases it would have been sufficient to use the System.Runtime.InteropServices.UnRegisterTypeForCOMClients
        /// but this would not be enough in this special scenario since there would be some missing keys for uninstalling a 
        /// toolbar.
        /// </summary>
        /// <param name="clsid"></param>
        public static void UnRegisterCOMObject(Guid clsid)
        {
            try
            {
                var id = clsid.ToString("B");
                RegistryKey rCLSID = Registry.ClassesRoot.OpenSubKey("CLSID", true);
                if (rCLSID != null)
                {
                    string[] keys = rCLSID.GetSubKeyNames();
                    foreach (string key in keys)
                    {
                        if (key == id)
                        {
                            rCLSID.DeleteSubKeyTree(id);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "Exception: UnRegisterCOMObject " + clsid + ": " + ex);
            }
        }
        
        public static void RegisterHost(Type typeToRegister, string GUID, string PROGID)
        {        
            TraceSink.TraceEvent(TraceEventType.Information, 0, "RegisterHost RegisterFunction");

            try
            {
                var key = Registry.LocalMachine.CreateSubKey(SpicIE.Constants.REGISTRY_BROWSERHELPEROBJECTS + "{" + GUID + "}");
                key.SetValue("NoExplorer", (object)1);
                key.Close();
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "Exception: RegisterHost " + GUID + ": " + ex);
            }
        }

        public static void UnregisterHost(Type typeToRegister, string GUID)
        {
            TraceSink.TraceEvent(TraceEventType.Information, 0, "RegisterHost UnregisterHost");

            try
            {
                var ID = new Guid(GUID).ToString("B");

                RegistryKey rk = Registry.LocalMachine.OpenSubKey(SpicIE.Constants.REGISTRY_BROWSERHELPEROBJECTS, true);
                if(rk != null)
                {
                    RegistryKey rkSub = rk.OpenSubKey(ID, true);
                    if(rkSub != null)
                    {
                        rk.DeleteSubKey(ID, false);
                    }
                }
            }
            catch (Exception ex)
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "Exception: UnregisterHost " + GUID + ": " + ex);
            }
        }

        public static bool RegisterControls(IEnumerable<IControlBase> controls)
        {
            if (controls != null && controls.Any())
            {
                foreach (IControlBase control in controls)
                {
                    TraceSink.TraceInformation("Registering Control");
                    control.Register();
                }
            }
            else
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "No Controls in RegisterControls");
            }

            return true;
        }

        public static bool UnregisterControls(IEnumerable<IControlBase> controls)
        {
            if (controls != null && controls.Any())
            {
                foreach (IControlBase control in controls)
                {
                    TraceSink.TraceInformation("Unregistering Control");
                    control.Unregister();
                }
            }
            else
            {
                TraceSink.TraceEvent(TraceEventType.Error, 0, "No Controls in UnregisterControls");
            }

            return true;
        }
        #endregion
    }
}
